/*
 * Session.cpp
 *
 *  Created on: 2013. 9. 14.
 *      Author: jjaehuny
 */

#include "Session.h"
#include "User.h"

#include "protocol.h"
#include "helper.h"
#include "Proxy.h"
#include "NLogic/Function.h"

#include "system/config.h"
#include "NState/Static.h"
#include "NLogic/Static.h"
#include "NLogic/Party.h"
#include "NLogic/Space.h"
#include "Config.h"

#include "NLogic/ServerAddrCCU.h"
#include "NResource/Static.h"

#include "Static.h"

namespace ProjectZ {

Session::Session() : _user(NULL)
{
	_bClose = true;
}

Session::~Session() {
}

int
Session::SendPacket(::Network::Packet * pPacket, bool bStatus, bool bOneShot)
{
	if (true == bOneShot)
	{
		if (true == pPacket->GetEncrypt())
			::ProjectZ::Encryption::Instance().Encrypt(pPacket);

		const int sendflags = MSG_DONTWAIT;
		int nSendSize = send(_GetSocket(), pPacket->GetBasePtr(), pPacket->GetBaseLen(), sendflags);
		if (nSendSize == -1)// || errno != EAGAIN)
		{
			short cmd = pPacket->GetCommand_SendPacket();
			DEBUGLOG("SENDFAIL socket : %d cmd : 0x%x, nSendSize == -1 , errno %d", _GetSocket(), cmd, errno);
		}

		::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
		return nSendSize;
	}

	if (NULL != _user)
	{   // 유저 요청이 1분 동안 없었던 유저
		unsigned long nowTime = ProjectZ::NLogic::NFunction::GetTickCount();
		if (nowTime - _user->GetLastLatency() > 1000 * 60)
		{
			::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//			{
//				std::lock_guard<std::mutex> lock(_mutex_Sync);
//				std::for_each(_clsWaitSyncSender.begin(), _clsWaitSyncSender.end(), [&](::Network::Packet * pPacket) {
//					::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//				} );
//				_clsWaitSyncSender.clear();
//			}
			return 0;
		}
	}

//	{
//		std::lock_guard<std::mutex> lock(_mutex_Sync);
//		if (_clsWaitSyncSender.size() < 100)
//		{
//			_clsWaitSyncSender.push_back(pPacket);
//			Static::Instance().GetThreadPool().PostTask(std::bind(&Session::work, this));
//		}
//		else
//		{
//			::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//
//			std::for_each(_clsWaitSyncSender.begin(), _clsWaitSyncSender.end(), [&](::Network::Packet * pPacket) {
//				::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//			} );
//			_clsWaitSyncSender.clear();
//		}
//	}

	{
		std::lock_guard<std::mutex> lock(_mutex_Sync);
		_clsWaitSyncSender.push_back(pPacket);
	}

	Static::Instance().GetThreadPool().PostTask(std::bind(&Session::work, this));

	return 0;
}

int
Session::SendPacketAsync(::Network::Packet * pPacket)
{
	pPacket->Capture();

	if (NULL != _user)
	{   // 유저 요청이 1분 동안 없었던 유저
		unsigned long nowTime = ProjectZ::NLogic::NFunction::GetTickCount();
		if (nowTime - _user->GetLastLatency() > 1000 * 60)
		{
			::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//			{
//				std::lock_guard<std::mutex> lock(_mutex_Async);
//				std::for_each(_clsWaitAsyncSender.begin(), _clsWaitAsyncSender.end(), [&](::Network::Packet * pPacket) {
//					::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//				} );
//				_clsWaitAsyncSender.clear();
//			}
			return 0;
		}
	}

//	{
//		std::lock_guard<std::mutex> lock(_mutex_Async);
//		if (_clsWaitAsyncSender.size() < 100)
//		{
//			_clsWaitAsyncSender.push_back(pPacket);
//			Static::Instance().GetThreadPool().PostTask(std::bind(&Session::workAsync, this));
//		}
//		else
//		{
//			::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//
//			std::for_each(_clsWaitAsyncSender.begin(), _clsWaitAsyncSender.end(), [&](::Network::Packet * pPacket) {
//				::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
//			} );
//			_clsWaitAsyncSender.clear();
//		}
//	}

	{
		std::lock_guard<std::mutex> lock(_mutex_Async);
		_clsWaitAsyncSender.push_back(pPacket);
	}

	Static::Instance().GetThreadPool().PostTask(std::bind(&Session::workAsync, this));

	return 0;
}

void
Session::OnPacket()
{
	unsigned short command;

	::Network::Packet* pPacket;
	this->PreDequePacket();
	while ((pPacket = this->DequeuePacket_NoLock()))
	{
		if ( pPacket->GetType() == ::Network::Packet::OPEN)
		{
			_bClose = false;
			DEBUGLOG("[SESSION OPEN IP : %s:%d]", this->GetNetworkPeerInfo().ip.c_str(), this->GetNetworkPeerInfo().port);
			PROCESSLOG("SOCIALID SOCKET CONNECT : %d", this->_GetSocket());
			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			continue;
		}

		if ( pPacket->GetType() == ::Network::Packet::CLOSE)
		{
			this->OnLogoutGuildMember();

			if (true == _bClose)
			{
				DEBUGLOG("ERROR SESSION IS ALEADY CLOSE : %d", this->_GetSocket());
				::Network::GETPACKETPOOL()->READ_DEL(pPacket);
				return;
			}

			this->onClose(pPacket);
			_bClose = true;
			return;
		}

		command = pPacket->GetCommand();

//		short serial = 0;
//		unsigned char clientCRC = 0;

		if (::ProjectZ::Config::Instance().GetResourceDatabase()._enumNational == CH)
		{
//			serial = pPacket->GetSerial();
//			clientCRC = pPacket->GetCRC();

			//DEBUGLOG("CH OnPacket serial : %d crc : %d", serial, clientCRC);
		}

		if (CMD_CS_REQ_SERVER_ADDR == command)
		{
			this->on_CS_REQ_SERVER_ADDR(pPacket);
			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			continue;
		}

		if (CMD_CS_CONNECT == command)
		{
			if (false == this->on_CS_CONNECT(pPacket))
				continue;
		}

		if (CMD_CS_RECONNECT == command)
		{
			if (false == this->on_CS_RECONNECT(pPacket))
				continue;
		}

		if (NULL == _user)
		{
			if (CMD_CS_PING != command)
			{
				DEBUGLOG("[SESSION : NULL == _user command : 0x%x", command);
			}

			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			continue;
		}

		Session * pHasSession = _user->GetSession();
		if (NULL == pHasSession)
		{
			DEBUGLOG("[SESSION : NULL == pHasSession State : %s pCommand : 0x%x GID : %d", _user->GetState()->GetName().c_str(), command, _user->GetUserSeq());
			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			return;
		}

		if (this != _user->GetSession())
		{
			DEBUGLOG("[SESSION : this != _user->GetSession() State : %s pCommand : 0x%x GID : %d", _user->GetState()->GetName().c_str(), command, _user->GetUserSeq());
			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			return;
		}

		User::State * pState = _user->GetState();
		if (NULL == pState)
		{
			DEBUGLOG("[SESSION : NULL == pState pCommand : 0x%x", command);
			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			continue;
		}

		User::State::Command pCommand = pState->GetCommand(command);
		if (NULL == pCommand)
		{
			DEBUGLOG("[SESSION : NULL == pCommand : 0x%x State : %s]", command, pState->GetName().c_str());
			::Network::GETPACKETPOOL()->READ_DEL(pPacket);
			continue;
		}

		unsigned long starttick = NLogic::NFunction::GetTickCount();
		{
			char cmd[80];
			STRESSLOG("[READCOMMAND: 0x%x %s GID : %d]", command, GetCommandName(cmd, command), _user->GetUserSeq());
		}

		if (::ProjectZ::Config::Instance().GetResourceDatabase()._enumNational == CH)
		{
//			if ( serial != _user->GetSerial())
//			{
//				DEBUGLOG("SERIAL IS NO EQUAL SERVER SERIAL = %d CLIENT SERIAL : %d COMMAND : 0x%x", _user->GetSerial(), serial, command);
//				::Network::GETPACKETPOOL()->READ_DEL(pPacket);
//				continue;
//			}
		}

		if (::ProjectZ::Config::Instance().GetResourceDatabase()._enumNational == CH)
		{
//			unsigned char serverCRC = this->calcCheckSum(pPacket->GetPtr()+9, pPacket->GetEndLen()-9);
//
//			if (serverCRC != clientCRC)
//			{
//				DEBUGLOG("CRC IS NO EQUAL SERVER CRC = %d CLIENT CRC : %d COMMAND : 0x%x", serverCRC, clientCRC, command);
//				::Network::GETPACKETPOOL()->READ_DEL(pPacket);
//				continue;
//			}
		}



		if (CMD_CS_PING != command)
		{
			DEBUGLOG("user command : 0x%x", command);
		}

		pCommand(_user, pPacket);

		_user->AddSerial();

		{
			char cmd[80];
			STRESSLOG("[READCOMMAND: 0x%x %s GID : %d process time : %u]",
					command,
					GetCommandName(cmd, command),
//					_user->GetUserInfo().userseq,
					_user->GetUserSeq(),
					NLogic::NFunction::GetTickCount() - starttick);

			unsigned long endTick = NLogic::NFunction::GetTickCount();
			unsigned long gab = endTick - starttick;
			if (gab > 1000)
			{
				::Logger::GETLOG()->ToAll(::Logger::OBSERVER, "[READCOMMAND: 0x%x %s GID : %d process time : %u]",
					command,
					GetCommandName(cmd, command),
					_user->GetUserSeq(),
					NLogic::NFunction::GetTickCount() - starttick);
			}
		}
		::Network::GETPACKETPOOL()->READ_DEL(pPacket);
	}
}

void
Session::onClose(::Network::Packet * pPacket)
{
	PROCESSLOG("[CLOSE IP : %s:%d]", this->GetNetworkPeerInfo().ip.c_str(), this->GetNetworkPeerInfo().port);
	PROCESSLOG("SOCIALID SOCKET DISCONNECT : %d", this->_GetSocket());

	{
		std::lock_guard<std::mutex> lock(_mutex_Async);
		std::for_each(_clsWaitAsyncSender.begin(), _clsWaitAsyncSender.end(), [&](::Network::Packet * pPacket) {
			::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
		} );
		_clsWaitAsyncSender.clear();
	}

	{
		std::lock_guard<std::mutex> lock2(_mutex_Sync);
		std::for_each(_clsWaitSyncSender.begin(), _clsWaitSyncSender.end(), [&](::Network::Packet * pPacket) {
			::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
		} );
		_clsWaitSyncSender.clear();
	}


	::Network::GETPACKETPOOL()->READ_DEL(pPacket);
	::Network::GETSESSIONPOOL()->DEL(this);

	if (NULL == _user)
	{
		return;
	}


	_user->SetSession(NULL);
	//this->_user = NULL;
	User * pUser = _user;
	_user = NULL;

	User::State * pState = pUser->GetState();
	if (NULL == pState)
	{
		return;
	}

	pState->OnClose(pUser, true);
}


void
Session::OnLogoutGuildMember()
{
	if (NULL == _user)
		return;

	LogoutGuildMemberSyn syn;
	syn.user_seq	= _user->GetUserSeq();
	syn.guild_seq	= _user->GetUserInfo().guild_seq;

	NProxy::SendGuildServer(_user, syn);
}


void
Session::on_CS_REQ_SERVER_ADDR(::Network::Packet * pPacket)
{
	//STRESSLOG("[CHANNEL] CS_REQ_SERVER_ADDR::ONEXECUTE");

	Toolkit::Str<32> socialid;
	Encryption::Instance().Decrypt_First(pPacket);

	//short totalSize = * ((short *)(pPacket->GetPtr() + 1));
	//short len = * ((short *)(pPacket->GetPtr() + 9));
	//DEBUGLOG("on_CS_REQ_SERVER_ADDR totalSize : %d serial : %d crc : %d string len : %d LEN : %d", totalSize, 0, 0, len, pPacket->GetLen());

	*pPacket >> socialid;

	//DEBUGLOG("on_CS_REQ_SERVER_ADDR SOCIALID : %s", socialid.c_str());

	if(true == ::ProjectZ::Config::Instance().GetResourceDatabase()._isBlock)
	{
		if( false == NResource::Static::Instance().GetAllowUserResource()->IsAllowUser(socialid.c_str()) )
			return;
	}

	if (false == ::ProjectZ::Config::Instance().GetResourceDatabase()._isTest)
	{
		int maxConnectUserInServer = ::ProjectZ::Static::Instance().GetMaxConnectUserCountInServer();
		int maxCount = static_cast<int>( maxConnectUserInServer * 0.9 );
		::ProjectZ::NLogic::ServerAddrCCU * clsSvrAddr = ::ProjectZ::NLogic::Static::Instance().GETSERVERADDRCCU();
		auto & svrAddrTree = clsSvrAddr->GetServerAddrTree();

		auto iter = svrAddrTree.begin();
		for(; iter != svrAddrTree.end(); ++iter)
		{
			if( maxCount <= iter->second._ccu )
			{
				DEBUGLOG("SERVER INDEX : %d maxConnectUserInServer : %d CCU : %d", iter->second._serverKey, maxCount, iter->second._ccu);
				continue;
			}

			break;
		}

		if (iter == svrAddrTree.end())
		{
			DEBUGLOG("%s iter == svrAddrTree.end()", __FUNCTION__);
			return;
		}

		::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
		*pAck << iter->second._domainName.c_str() << static_cast<uint16_t>(iter->second._port) << static_cast<uint16_t>(iter->second._serverKey) << static_cast<uint16_t>(0);
		pAck->MakePacket(CMD_SC_REQ_SERVER_ADDR);
		Encryption::Instance().Encrypt_First((unsigned char *)socialid.c_str(), socialid.Len(), pAck);
		pAck->SetNoEncryt();
		this->SendPacket(pAck, 1, true);
	}
	else
	{
		unsigned short port = ::ProjectZ::Config::Instance().GetResourceDatabase()._port;


		::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
		* pAck << ::ProjectZ::Config::Instance().GetResourceDatabase()._service_url.c_str() << port
				<< static_cast<uint16_t>(::ProjectZ::Config::Instance().GetResourceDatabase()._channelUniqueIndex)
				<< static_cast<uint16_t>(0);

		pAck->MakePacket(CMD_SC_REQ_SERVER_ADDR);

		Encryption::Instance().Encrypt_First((unsigned char *)socialid.c_str(), socialid.Len(), pAck);
		pAck->SetNoEncryt();
		this->SendPacket(pAck, 1, true);
	}
}

bool
Session::on_CS_CONNECT(::Network::Packet * pPacket)
{
	PROCESSLOG("SOCIALID SOCKET CMD_CS_CONNECT : %d", this->_GetSocket());

	if (NULL != _user)
	{
		DEBUGLOG("[SESSION : CMD_CS_CONNECT NULL != _user");
		PROCESSLOG("SOCIALID CMD_CS_CONNECT NULL != _user : %d", this->_GetSocket());

		::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
		pAck->Write2(-11);
		pAck->MakePacket(CMD_CS_CONNECT);

		this->SendPacket(pAck, 1);
		::Network::GETPACKETPOOL()->READ_DEL(pPacket);

		return false;
	}

	_user = ::ProjectZ::User::Factory::Instance().NEW();
	_user->SetSession(this);

	return true;
}

bool
Session::on_CS_RECONNECT(::Network::Packet * pPacket)
{
	if (NULL != _user)
	{
		::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
		pAck->Write2(ACK_OK);
		pAck->MakePacket(CMD_SC_RECONNECT);
		this->SendPacket(pAck, 1);

		_user->CheckChief();
		_user->ResetSerial();

		::Network::GETPACKETPOOL()->READ_DEL(pPacket);
		return false;
	}

	PROCESSLOG("[SESSION : CMD_CS_RECONNECT NULL != _user->GetSession()");
	PROCESSLOG("SOCIALID CMD_CS_RECONNECT : %d", this->_GetSocket());

	int type = 0;
	int old_gid = 0;
	int server_id = 0;
	int channel_id = 0;

	pPacket->ParseMsg("%c%d%c%2d", &type, &old_gid, &server_id, &channel_id);

	_user = ::ProjectZ::NProxy::GetUser(old_gid);
	if (NULL == _user)
	{
		::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
		pAck->Write2(-1);
		pAck->MakePacket(CMD_SC_RECONNECT);
		this->SendPacket(pAck, 1);

		::Network::GETPACKETPOOL()->READ_DEL(pPacket);
		return false;
	}
	else
	{
//		NLogic::Space * pSpace = _user->GetSpace();
//		if (pSpace != NULL && pSpace->GetData()._spaceType == NLogic::Space::SpaceType::WORLD)
//		{
//			pSpace->LeaveUser(_user);
//			pSpace->EnterUser(_user);
//			_user->SetEnumState(User::EnumState::__NONE__);
//		}

		Session * pOldSession =  _user->GetSession();
		if (NULL == pOldSession)
		{
			PROCESSLOG("[SESSION : CMD_CS_RECONNECT NULL == pOldSession");
			NLogic::Space * pSpace = _user->GetSpace();
			if (pSpace != NULL && pSpace->GetData()._spaceType == NLogic::Space::SpaceType::WORLD)
			{
				pSpace->LeaveUser(_user);
				pSpace->EnterUser(_user);
				_user->SetEnumState(User::EnumState::__NONE__);
			}

			_user->SetSession(this);

			return true;
		}

		if (pOldSession == this)
		{
			if (_user->GetState() != NState::Static::Instance().MAINFRIENDLIST())
			{
				::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
				pAck->Write2(-1);
				pAck->MakePacket(CMD_SC_RECONNECT);
				this->SendPacket(pAck, 1);

				::Network::GETPACKETPOOL()->READ_DEL(pPacket);
				return false;
			}

			PROCESSLOG("[SESSION : CMD_CS_RECONNECT pOldSession == this");
			::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
			* pAck << static_cast<int16_t>(ACK_OK);
			pAck->MakePacket(CMD_SC_RECONNECT);
			this->SendPacket(pAck, 1);

			_user->CheckChief();
			_user->ResetSerial();

			return false;
		}


		if (pOldSession != this)
		{
			PROCESSLOG("[SESSION : CMD_CS_RECONNECT pOldSession != this");

			if (_user->GetState() != NState::Static::Instance().MAINFRIENDLIST())
			{
				::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
				pAck->Write2(-1);
				pAck->MakePacket(CMD_SC_RECONNECT);
				this->SendPacket(pAck, 1);

				::Network::GETPACKETPOOL()->READ_DEL(pPacket);
				return false;
			}

			_user->SetSession(this);

			pOldSession->ResetUser();
			pOldSession->Close();

			::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
			* pAck << static_cast<int16_t>(ACK_OK);
			pAck->MakePacket(CMD_SC_RECONNECT);
			this->SendPacket(pAck, 1);

			_user->CheckChief();
			_user->ResetSerial();

			return false;
		}

		//_user->OnReconnect();

//		::Network::Packet * pAck = ::Network::GETPACKETPOOL()->SEND_NEW();
//		pAck->Write2(1);
//		pAck->MakePacket(CMD_SC_RECONNECT);
//		this->SendPacket(pAck, 1);
//
//		::Network::GETPACKETPOOL()->READ_DEL(pPacket);
		return false;
	}
}

void
Session::work()
{
	::Network::Packet * pPacket = NULL;
	while (true)
	{
		{
			std::lock_guard<std::mutex> lock(_mutex_Sync);
			if (_clsWaitSyncSender.size() <= 0 )
				break;

			pPacket = _clsWaitSyncSender.front();
			_clsWaitSyncSender.pop_front();
		}

		if (true == pPacket->GetEncrypt())
			::ProjectZ::Encryption::Instance().Encrypt(pPacket);

		const int sendflags = MSG_DONTWAIT;
		int nSendSize = send(_GetSocket(), pPacket->GetBasePtr(), pPacket->GetBaseLen(), sendflags);
		if (nSendSize == -1 )//&& errno != EAGAIN)
		{
			short cmd = pPacket->GetCommand_SendPacket();
			DEBUGLOG("SENDFAIL socket : %d cmd : 0x%x, nSendSize == -1 , errno %d", _GetSocket(), cmd, errno);
		}

		if (pPacket->GetRefrenceCount() <= 0)
		{
			unsigned short command = 0;
			command = pPacket->GetCommand_SendPacket();
			char cmd[80];
			DEBUGLOG("DUPLICATE PACKET: 0x%x %s", command, GetCommandName(cmd, command));
		}
		::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
	}
}

void
Session::workAsync()
{
	::Network::Packet * pPacket = NULL;
	while (true)
	{
		{
			std::lock_guard<std::mutex> lock(_mutex_Async);
			if (_clsWaitAsyncSender.size() <= 0 )
				break;

			pPacket = _clsWaitAsyncSender.front();
			_clsWaitAsyncSender.pop_front();
		}

		::Network::Packet * pTemp = ::Network::GETPACKETPOOL()->SEND_NEW();
		memcpy(pTemp->GetBasePtr(), pPacket->GetBasePtr(), pPacket->GetBaseLen());
		pTemp->m_nEnd = pPacket->m_nEnd;
		pTemp->m_nOffset = pPacket->m_nOffset;
		pTemp->m_nHeaderLen = pPacket->m_nHeaderLen;

		if (true == pPacket->GetEncrypt())
			::ProjectZ::Encryption::Instance().Encrypt(pTemp);

		const int sendflags = MSG_DONTWAIT;
		int nSendSize = send(_GetSocket(), pTemp->GetBasePtr(), pTemp->GetBaseLen(), sendflags);
		if (nSendSize == -1 )//&& errno != EAGAIN)
		{
			short cmd = pTemp->GetCommand_SendPacket();
			DEBUGLOG("SENDFAIL socket : %d cmd : 0x%x, nSendSize == -1 , errno %d", _GetSocket(), cmd, errno);
		}

		::Network::GETPACKETPOOL()->SEND_DEL(pTemp);
		::Network::GETPACKETPOOL()->SEND_DEL(pPacket);
	}
}

unsigned char
Session::calcCheckSum(char* pData, int len)
{
	DEBUGLOG("%s len : %d", __FUNCTION__, len);

	unsigned int nSum = 0;
	for(int i=0; i<len; i++)
		nSum += pData[i];

	return (unsigned char)nSum;
}

} /* namespace ProjectZ */
